#ifndef SHADER_H
#define SHADER_H

class Shader
{
public:
	Shader () {}
	Shader (const char * vp_source, const char * fp_source) 
	{
		load(vp_source, fp_source);
	}
	static void init ()
	{
		int major;
		int minor;
		getGLVersion(& major, & minor);
//		std::cout<<major<<' '<<minor<<std::endl;

		getGLSLVerstion(&major, & minor);
//		std::cout<<major<<' '<<minor<<std::endl;

		glewInit();
		if (glewIsSupported("GL_VERSION_2_0")) ;
//			printf("Ready for OpenGL 2.0\n");
		else {
			throw("OpenGL 2.0 not supported\n");
		}
		
	}
	void load (const char * vp_source, const char * fp_source) 
	{
		v = glCreateShader(GL_VERTEX_SHADER);
		f = glCreateShader(GL_FRAGMENT_SHADER);

		char * vs = load_source(vp_source);
		char * fs = load_source(fp_source);

		const char * vv = vs;
		const char * ff = fs;

		glShaderSource(v, 1, &vv, NULL);
		glShaderSource(f, 1, &ff, NULL);

		free((void *)vs);free((void *)fs);

		glCompileShader(v);
		glCompileShader(f);

		printShaderInfoLog(v);
		printShaderInfoLog(f);

		p = glCreateProgram();
		glAttachShader(p,v);
		glAttachShader(p,f);

		glLinkProgram(p);
		printProgramInfoLog(p);

	}

	void bind()
	{
		glUseProgram(p); 		
	}

	void unbind () 
	{
		glUseProgram(0);
	}
	int get_uniform(const char * uniform_name) const 
	{
		return glGetUniformLocation( p, uniform_name );
	}
	void set_uniform(const char * uniform_name, int i) const 
	{
		glUniform1i( glGetUniformLocation( p, uniform_name), i );
	}
	void set_uniform(const char * uniform_name, float f) const 
	{
		glUniform1f( glGetUniformLocation( p, uniform_name), f);
	}
	void set_uniform4f (const char * uniform_name, float v[4])
	{
		glUniform4f(glGetUniformLocation(p, uniform_name), v[0], v[1], v[2], v[3]);
	}
	void set_uniform(const char * uniform_name, const float vector4[], unsigned size) const 
	{
		glUniform4fv(glGetUniformLocation(p, uniform_name), size,  vector4);
	}

	static void getGLVersion(int * major, int * minor)
	{
		const char * verstr = (const char *) glGetString (GL_VERSION) ;
		if ((verstr == NULL) || (sscanf(verstr, "%d.%d", major, minor) != 2))
		{
			* major = * minor = 0;
			fprintf(stderr, "Invalid GL_VERSION format!!! \n");
		}
	}
	static void getGLSLVerstion(int * major, int * minor)
	{
		int gl_major, gl_minor;
		getGLVersion(& gl_major, &gl_minor);
		if (gl_major >= 2)
		{
			const char * verstr = (const char *) glGetString (GL_SHADING_LANGUAGE_VERSION);
			if ((verstr == NULL) || (sscanf(verstr, "%d.%d", major, minor) != 2))
			{	
				* major = * minor = 0;
				fprintf(stderr, "Invalid GL_VERSION format!!! \n");
			}
		}
	}
	GLuint get_handle () 
	{
		return p ;
	}
protected:
	
	int printOglError(char *file, int line)
	{
		//
		// Returns 1 if an OpenGL error occurred, 0 otherwise.
		//
		GLenum glErr;
		int    retCode = 0;

		glErr = glGetError();
		while (glErr != GL_NO_ERROR)
		{
			printf("glError in file %s @ line %d: %s\n", file, line, gluErrorString(glErr));
			retCode = 1;
			glErr = glGetError();
		}
		return retCode;
	}
	void printShaderInfoLog(GLuint obj)
	{
		int infologLength = 0;
		int charsWritten  = 0;
		char *infoLog;
		glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
		if (infologLength > 0)
		{
			infoLog = (char *)malloc(infologLength);
			glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
			printf("Shader Info Log %s\n",infoLog);
			free(infoLog);
		}
	}

	void printProgramInfoLog(GLuint obj)
	{
		int infologLength = 0;
		int charsWritten  = 0;
		char *infoLog;

		glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
		if (infologLength > 0)
		{
			infoLog = (char *)malloc(infologLength);
			glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
			printf("Program Info Log %s\n",infoLog);
			free(infoLog);
		}
	}


	char * load_source(const char *fn)
	{
		FILE *fp;
		char *content = NULL;
		int count=0;
		if (fn != NULL) {
			fp = fopen(fn,"rt");
			std::string err (fn);
			err += " File doesn't exist";
			TEST (fp, err.c_str());
			if (fp != NULL) {
				fseek(fp, 0, SEEK_END);
				count = ftell(fp);
				rewind(fp);

				if (count > 0) {
					content = (char *)malloc(sizeof(char) * (count+1));
					count = fread(content,sizeof(char),count,fp);
					content[count] = '\0';
				}
				fclose(fp);
			}
		}
		return content;
	}

	int textFileWrite(char *fn, char *s)
	{
		FILE *fp;
		int status = 0;

		if (fn != NULL) {
			fp = fopen(fn,"w");
			if (fp != NULL) {
				if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
					status = 1;
				fclose(fp);
			}
		}
		return(status);
	}

private:
	GLuint v,f,p;
} ;

#endif
